
	/************************************************************************************************

		@API
		Class: 			jsl.TextFile
		inherits:		jsl.File

		Constructor:
			file			jsl.TextFile				(path*, encoding*)

		Methods:
			ln				readLine						(n*)
			tf				skipLine						(n*)
			tf				writeLine						(s)
			tf				appendLine					(s)

			en				encoding						(encoding*)

		Ovewrite:
			s					read								(n*)
			tf				write								(s)
			tf				append							(s)

		Static:
			ln				readLine						(path, (n|en)*, en*)
			tf				skipLine						(path, (n|en)*, en*)
			tf				writeLine						(path, s			, en*)
			tf				appendLine					(path, s			, en*)

			en				encoding						(encoding*)

			s					read								(path, (n|en)*, en*)
			tf				write								(path, s			, en*)
			tf				append							(path, s			, en*)

		Note:
			en: ascii, iso-8859-1, utf8, utf16, utf16be, utf16le
			ln:	string/array[s]

	************************************************************************************************/

	//---------------------------------------------------------------------------
	jsl.require("data")

	//---------------------------------------------------------------------------
	jsl.Class("jsl.TextFile", {

		//---------------------------------------------------------------------------
		inherits: jsl.File,

		initializer: function() {
			var File = jsl.File

			for(var id in File)
				if(id.slice(0, 2) == "__")
					this[id] = File[id]

			//---------------------------------------------------------------------------
			__ni_fle_init && __ni_fle_init(this)
			this.name = function(path) {//!
				if(arguments.length !== 1)	throw jsl.ParamError()//!
				return this.__nme(path)
			}
		},

		//Encoding data
		//---------------------------------------------------------------------------
		$__ens:	[ "ascii"			, "iso-8859-1"			, "utf8"			, "utf16"			, "utf16-be"			, "utf16-le"			, "utf16-be*"			, "utf16-le*" 	 ],
		$__chs:	{ "ascii": "1", "iso-8859-1": "1"	, "utf8": "3d", "utf16": "2", "utf16-be": "2"	, "utf16-le": "2"	, "utf16-be*": "2", "utf16-le*":"2"},
		$__mks:	{ "ascii": 0	, "iso-8859-1": 0		, "utf8":	0		, "utf16":	2	, "utf16-be": 0		, "utf16-le": 0		, "utf16-be*": 2	, "utf16-le*":2}  ,
		$__mds:	{ "utf16": ["\u00fe\u00ff", "\u00ff\u00fe"], "utf16-be*": ["\u00fe\u00ff"], "utf16-le*": ["\u00ff\u00fe"], "\u00fe\u00ff": "utf16-be*",  "\u00ff\u00fe": "utf16-le*" },

		//Data
		//---------------------------------------------------------------------------
		$__en:		"utf8",
		$__tix:		0,
		$__ix:		0,

		__en: 			"utf8",
		__tix:			0,
		__ix:				0,

		//---------------------------------------------------------------------------
		TextFile: function(path, encoding) {
			var f//!
			if(arguments.length > 2)		throw jsl.ParamError()//!

			if(arguments.length)				f = jsl.TextFile.__path(path)//!
			if(arguments.length === 2) 	jsl.validate(encoding, "s+", "encoding")
			if(arguments.length === 2)	jsl.validate(encoding.toLowerCase(), "s(" + jsl.TextFile.__ens.join(",") + ")", "encoding")//!

			this.__en = encoding ? encoding.toLowerCase() : jsl.TextFile.__en
			if(arguments.length)	this.File(f)
			else									this.File()
		},

		//Static
		//---------------------------------------------------------------------------
		$readLine: function(path, n, en) {//!
			if(!arguments.length || arguments.length > 3)	throw jsl.ParamError()//!

			if(arguments.length === 1)													return this.__readLine(path, 0, this.__en, 1)
			else if(arguments.length === 2 && jsl.isString(n))	return this.__readLine(path, 0, n, 1)
			else if(arguments.length === 2)											return this.__readLine(path, 0, this.__en, n)
			else																								return this.__readLine(path, 0, en, n)
		},
		//---------------------------------------------------------------------------
		$skipLine: function(path, n, en) {//!
			if(!arguments.length || arguments.length > 3)	throw jsl.ParamError()//!

			if(arguments.length === 1)													this.__readLine(path, 0, this.__en, 1)
			else if(arguments.length === 2 && jsl.isString(n))	this.__readLine(path, 0, n, 1)
			else if(arguments.length === 2)											this.__readLine(path, 0, this.__en, n)
			else																								this.__readLine(path, 0, en, n)
			return new this(path)
		},
		//---------------------------------------------------------------------------
		$writeLine: function(path, s, en) {//!
			if(arguments.length < 2 || arguments.length > 3)	throw jsl.ParamError()//!

			if(arguments.length === 2)	this.__writeLine(path, s, 0, this.__en)
			else												this.__writeLine(path, s, 0, en)
			return new this(path)
		},
		//---------------------------------------------------------------------------
		$appendLine: function(path, s, en) {//!
			if(arguments.length < 2 || arguments.length > 3)	throw jsl.ParamError()//!

			if(arguments.length === 2)	this.__appendLine(path, s, this.__en)
			else												this.__appendLine(path, s, en)
			return new this(path)
		},
		//---------------------------------------------------------------------------
		$encoding: function(encoding) {
			var v//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			jsl.validate(encoding, "s+", "encoding")
			if(arguments.length)			jsl.validate(encoding.toLowerCase(), "s(" + this.__ens.join(",") + ")", "encoding")//!

			if(arguments.length) {
				this.__en = encoding.toLowerCase()
				v = this
			}
			else
				v = this.__en

			return v
		},
		//---------------------------------------------------------------------------
		$read: function(path, n, en) {//!
			if(!arguments.length || arguments.length > 3)	throw jsl.ParamError()//!

			if(arguments.length === 1)													return this.__read(path, 0, this.__en)
			else if(arguments.length === 2 && jsl.isString(n))	return this.__read(path, 0, this.__en, this.__size(path))
			else if(arguments.length === 2)											return this.__read(path, 0, this.__en, n)
			else																								return this.__read(path, 0, en, n)
		},
		//---------------------------------------------------------------------------
		$write: function(path, s, en) {//!
			if(arguments.length < 2 || arguments.length > 3)	throw jsl.ParamError()//!

			if(arguments.length === 2)	this.__write(path, s, 0, this.__en)
			else												this.__write(path, s, 0, en)
			return new this(path)
		},
		//---------------------------------------------------------------------------
		$append: function(path, s, en) {//!
			if(arguments.length < 2 || arguments.length > 3)	throw jsl.ParamError()//!

			if(arguments.length === 2)	this.__append(path, s, this.__en)
			else												this.__append(path, s, en)
			return new this(path)
		},

		//Regular
		//---------------------------------------------------------------------------
		readLine: function(n) {
			var ln//!
			if(arguments.length > 1)	throw jsl.ParamError()//!

			if(arguments.length)	ln = jsl.TextFile.__readLine(this.__f, this.__ix, this.__en, n)
			else									ln = jsl.TextFile.__readLine(this.__f, this.__ix, this.__en, 1)

			//ln == "" can increment __ix of the encoding of \n
			this.__ix	 += jsl.TextFile.__ix
			this.__tix += jsl.TextFile.__tix
			return ln
		},
		//---------------------------------------------------------------------------
		skipLine: function(n) {//!
			if(arguments.length > 1)	throw jsl.ParamError()//!

			if(arguments.length)	jsl.TextFile.__readLine(this.__f, this.__ix, this.__en, n)
			else									jsl.TextFile.__readLine(this.__f, this.__ix, this.__en, 1)

			this.__ix	 += jsl.TextFile.__ix
			this.__tix += jsl.TextFile.__tix
			return this
		},
		//---------------------------------------------------------------------------
		writeLine: function(s) {//!
			if(arguments.length !== 1)	throw jsl.ParamError()//!
			jsl.TextFile.__writeLine(this.__f, s, this.__ix, this.__en)

			this.__ix	 += jsl.TextFile.__ix
			this.__tix += s.length
			return this
		},
		//---------------------------------------------------------------------------
		appendLine: function(s) {//!
			if(arguments.length !== 1)	throw jsl.ParamError()//!
			jsl.TextFile.__appendLine(this.__f, s, this.__en)

			this.__ix	 += jsl.TextFile.__ix
			this.__tix += s.length
			return this
		},
		//---------------------------------------------------------------------------
		encoding: function(encoding) {
			var v//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			jsl.validate(encoding, "s+", "encoding")
			if(arguments.length)			jsl.validate(encoding.toLowerCase(), "s(" + jsl.TextFile.__ens.join(",") + ")", "encoding")//!

			if(arguments.length) {
				this.__en = encoding.toLowerCase()
				v = this
			}
			else
				v = this.__en

			return v
		},
		//---------------------------------------------------------------------------
		read: function(n) {
			var cs//!
			if(arguments.length > 1)	throw jsl.ParamError()//!

			if(arguments.length)	cs = jsl.TextFile.__read(this.__f, this.__ix, this.__en, n)
			else									cs = jsl.TextFile.__read(this.__f, this.__ix, this.__en)

			this.__ix	 += jsl.TextFile.__ix
			this.__tix += cs.length
			return cs
		},
		//---------------------------------------------------------------------------
		write: function(s) {//!
			if(arguments.length !== 1)	throw jsl.ParamError()//!
			jsl.TextFile.__write(this.__f, s, this.__ix, this.__en)

			this.__ix	 += jsl.TextFile.__ix
			this.__tix += s.length
			return this
		},
		//---------------------------------------------------------------------------
		append: function(s) {//!
			if(arguments.length !== 1)	throw jsl.ParamError()//!
			jsl.TextFile.__append(this.__f, s, this.__en)
			return this
		}
	})

	//hidden: no stacktrace
	//---------------------------------------------------------------------------
	jsl.TextFile.__write = function(path, s, ix, en) {
		var f, i, bb, mk, md, n

		f = this.__path(path)//!
		i = this.__exists(f)

		if(!i)			throw jsl.ParamError("file.NotFound", [ f ])
		if(i === 2)	throw jsl.ParamError("file.opError"	, [ f ])
		jsl.validate(s	, "s"	, "s")
		jsl.validate(en	, "s+", "encoding")
		jsl.validate(en.toLowerCase(), "s(" + this.__ens.join(",") + ")", "encoding")
		this.__checkBom(f, en)//!

		if(s) {

			n		= this.__size(f)
			mk 	= this.__mks[en]

			if(mk)
				if(n)
					md = jsl.File.__read(f, 0, mk) + ""
				else	{
					md = this.__mds[en][0]
					jsl.File.__write(f, md, 0)
				}

			bb = jsl.Data.encode(s, mk ? this.__mds[md] : en)
			if(mk) bb.__bs.splice(0, mk)

			i = mk && ix < mk ? mk - ix : 0
			jsl.File.__write(f, bb, ix + i)

			this.__ix 	= bb.size() + i
			this.__tix	= s.length
		}
		else {
			this.__ix 	= 0
			this.__tix	= 0
		}
	}
	//---------------------------------------------------------------------------
	jsl.TextFile.__writeLine = function(path, s, ix, en) {//!
		jsl.validate(s, "s", "s")//!
		this.__write(path, ix ? "\n" + s : s, ix, en)
	}
	//---------------------------------------------------------------------------
	jsl.TextFile.__appendLine = function(path, s, en) {
		var n//!
		jsl.validate(s, "s", "s")//!

		n = this.__size(path)
		this.__write(path, n ? "\n" + s : s, n, en)
	}
	//---------------------------------------------------------------------------
	jsl.TextFile.__append = function(path, s, en) {
		this.__write(path, s, this.__size(path), en)
	}
	//---------------------------------------------------------------------------
	jsl.TextFile.__read = function(path, ix, en, n) {
		var f, i, s, nb, bb, d, mk, mkd

		f = this.__path(path)//!
		i = this.__exists(f)

		if(!i)											throw jsl.ParamError("file.NotFound", [ f ])
		if(i === 2)									throw jsl.ParamError("file.opError"	, [ f ])
		if(arguments.length === 4)	jsl.validate(n, "i++", "n")
		jsl.validate(en	, "s+", "encoding")
		jsl.validate(en.toLowerCase(), "s(" + this.__ens.join(",") + ")", "encoding")
		this.__checkBom(f, en)//!

		mk 	= this.__mks[en]
		d 	= this.__chs[en]
		s		= this.__size(f)

		if(mk && s) mkd = jsl.File.__read(f, 0, mk).__bs

		//nb = max bytes for code point supported in js for the current encoding
		//ex: utf16 -> 2 bytes (instead of 4)
		if(n) nb = d.charAt(0) * n
		else	nb = this.__size(f) - ix

		//nb + 3: to catch also a final code point error that could be discarded by the decoder if
		//not completely found
		d		= d.charAt(1)
		nb 	= d ? nb + 3 : nb
		i		= mk && ix < mk ? mk - ix : 0
		bb 	= jsl.File.__read(f, ix + i, nb)
		s 	= ""

		if(bb) {
			if(mk) bb.__bs.unshift.apply(bb.__bs, mkd)
			s = this.__decode(bb, en)

			if(s.length > n) {
				s		= s.slice(0, n)
				nb	= d ? jsl.Data.encode(s, en).size() - mk : s.length * this.__chs[en].charAt(0)
			}
			else
				nb = bb.__bs.length - mk

			this.__ix 	= nb + i
			this.__tix	= s.length
		}

		return s
	}
	//---------------------------------------------------------------------------
	jsl.TextFile.__decode = function(bb, en) {
		var d

		jsl.Data.__txt_stream = true

		try 		{ d = bb.decode(en)							}
		finally { jsl.Data.__txt_stream = false	}

		this.__rb = jsl.Data.__txt_rb
		return d
	}
	//---------------------------------------------------------------------------
	jsl.TextFile.__checkBom = function(f, en) {
		var mk, md, bb

		mk = this.__mks[en]

		if(mk && this.__size(f)) {
			md = this.__mds[en]
			bb = jsl.File.__read(f, 0, mk)

			for(var a = 0, l = md.length; a < l; a++)
				if(String.fromCharCode.apply(String, bb.__bs) === md[a])
					break

			if(a === l) throw jsl.ParamError("e.utf.BadUtf16Bom", [ f ])
		}
	}
	//---------------------------------------------------------------------------
	jsl.TextFile.__readLine = function(path, ix, en, n) {
		var f, i, nl, s, ls, sb, c, cs, l, nc, mk

		f = this.__path(path)//!
		i = this.__exists(f)
		if(!i)			throw jsl.ParamError("file.NotFound", [ f ])
		if(i === 2)	throw jsl.ParamError("file.OpError"	, [ f ])
		jsl.validate(en	, "s+", "encoding")
		jsl.validate(en.toLowerCase(), "s(" + this.__ens.join(",") + ")", "encoding")
		jsl.validate(n, "i++", "n")//!

		l = this.__size(f)

		if(ix < l) {

			nl	= /\n\r|\r\n|\n|\r/g
			s		= ""
			i		= ix
			nc	= n * 50

			do {
				sb = this.__read(f, i, en, nc)
				i += this.__ix

				if(sb) {
					c	= sb.slice(-1)

					if(c === "\n" || c === "\r") {
						cs = sb.slice(-2)

						if(i < l && cs !== "\n\r" && cs !== "\r\n") {
							sb = sb.slice(0, -1)
						}
					}

					s  += sb
					ls 	= s.split(nl, n + 1)
				}
			} while(sb && ls.length < n + 1)

			if(ls.length > n)
				ls.pop()

			sb = s.match(nl)
			if(sb) 	sb = sb.slice(0, n).join("")
			else		sb = ""

			s		= ls.join("") + sb
			mk	= this.__mks[en]

			if(this.__chs[en].charAt(1))	this.__ix	= jsl.Data.encode(s, en).size() - mk
			else													this.__ix	= s.length * this.__chs[en].charAt(0)

			this.__ix		= this.__ix + (mk && ix < mk ? mk - ix : 0)
			this.__tix	= s.length

			if(n == 1) ls = ls[0]
		}
		else
			ls = null

		return ls
	}
